package com.compomics.spectral.utilities.dataprovider.prideXML.extractor;

import com.compomics.spectral.utilities.dataprovider.prideXML.errors.enums.ConversionError;
import com.compomics.spectral.utilities.dataprovider.prideXML.errors.exceptions.XMLConversionException;
import com.compomics.spectral.utilities.dataprovider.prideXML.tools.GunZipper;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.List;
import org.apache.log4j.Logger;
import uk.ac.ebi.pride.jaxb.model.CvParam;
import uk.ac.ebi.pride.jaxb.model.Precursor;
import uk.ac.ebi.pride.jaxb.model.Spectrum;
import uk.ac.ebi.pride.jaxb.xml.PrideXmlReader;


/**
 * Hello world!
 *
 */
public class PrideXMLToMGFConverter {

    /**
     * Logger (includes file appender to log errors)
     */
    private static Logger logger;
    /**
     * Temporary file
     */
    private static File tempFile;
    /**
     * The current mgf file.
     */
    private File currentMgfFile;
    /**
     * The maximum precursor charge detected in the PRIDE XML files.
     */
    private int maxPrecursorCharge = 4;
    /**
     * The minmim precursor charge detected in the PRIDE XML files.
     */
    private int minPrecursorCharge = 1;
    /**
     * Instance of the unzipper class
     */
    private GunZipper gunZipper;
    /**
     * Source File (prideXML)
     */
    private File prideXMLUnzipped;
    /**
     * Extractor for the data from the PRIDEXML file
     */
    private PrideXmlReader reader;
    /**
     * private Converter --> singleton reasons (MGF)
     */
    private static PrideXMLToMGFConverter converter;
    /**
     * private exceptionhandler --> easier to get the errorlist from the
     * conversion
     */
    //  private static ConversionExceptionHandler errorHandler;
    /**
     * list for errors for pride
     *
     */
    private static List<ConversionError> errorList;
    private boolean zipped;
    private Double precursorMz;
    private Integer precursorCharge;
    private Double precursorIntensity;
    private String precursorChargeAsString;
    private Number[] mzBinaryArray;
    private Number[] intensityArray;
    private int spectrumID;

    private PrideXMLToMGFConverter() {
        gunZipper = GunZipper.getInstance();
    }

    public static PrideXMLToMGFConverter getInstance() {

        if (PrideXMLToMGFConverter.converter == null) {
//TODO solve this?            PropertyConfigurator.configure("src/main/resources/log4j.properties");
            PrideXMLToMGFConverter.logger = Logger.getLogger(PrideXMLToMGFConverter.class);
            PrideXMLToMGFConverter.converter = new PrideXMLToMGFConverter();
            errorList = new ArrayList<ConversionError>();
        }
        return converter;
    }

    public static boolean isGZipped(File f) throws FileNotFoundException {
        RandomAccessFile raf = new RandomAccessFile(f, "r");

        long n = 0L;
        try {
            n = raf.readInt();
            raf.close();
        } catch (IOException ex) {
            logger.error(ex);
        } finally {
            if (raf != null) {
                raf = null;
            }
        }
        if (n != 0L) {
            //1f 8b = magic number !
            String nHex = Long.toHexString(n);
            nHex = nHex.substring(0, 4);
            if (nHex.equals("1f8b")) {
                logger.debug("Unzipping file...");
                return true;
            } else {
                logger.error("This file is not a .gzip file");
                return false;
            }
        } else {
            return false;
        }
    }

    public static void setWorkingFile(File workingFile) {
        PrideXMLToMGFConverter.tempFile = workingFile;
    }

    public void setBufferSize(int bufferSize) {
        gunZipper.setBufferSize(bufferSize);
    }

    public int getBufferSize() {
        return gunZipper.getBufferSize();
    }

    public void init(File prideXML) {

        try {
            gunZipper = GunZipper.getInstance();
            zipped = isGZipped(prideXML);
            logger.debug("Procedure for " + prideXML.getAbsolutePath().toString());
            logger.info("Starting conversion...");
            logger.setLevel(org.apache.log4j.Level.ERROR);
            if (zipped) {
                //unzip method
                try {
                    String completeFileName = prideXML.getAbsolutePath().toString();
                    String[] unzippedFileNameArray = completeFileName.split(".xml");
                    prideXMLUnzipped = new File(unzippedFileNameArray[0] + ".xml");
                    tempFile = gunZipper.unzip(prideXML, prideXMLUnzipped);
                } catch (Exception ex) {
                    ex.printStackTrace();
                    logger.error("Could not unzip this file");
                }
            } else {
                tempFile = prideXML;
            }
            reader = new PrideXmlReader(tempFile);
        } catch (FileNotFoundException ex) {
            logger.error(ex);
        } finally {
            logger.setLevel(org.apache.log4j.Level.DEBUG);
            //TODO delete the temporary xml causes the reader to fail appearently : 
        }
    }

    public List<ConversionError> extractMGFFromPrideXML(File prideXML, File destinationMGF) throws XMLConversionException {

        if (convertPrideXmlToMgf(destinationMGF)) {
            logger.debug("Finished conversion !");
        } /*else {
         logger.error("Conversion could not be handled. Check the errorlog for extra details.");
         }*/
        //errors are no longer fatal ---> too many incorrectly annotated xml's
        return this.errorList;
    }

    private boolean validatePrecursor(Spectrum spectrum) throws NullPointerException, Exception {
        //1 Check for precursor mass
        Precursor precursor = spectrum.getSpectrumDesc().getPrecursorList().getPrecursor().get(0);
        List<CvParam> precursorCvParams = precursor.getIonSelection().getCvParam();
        for (CvParam cvParam : precursorCvParams) {
            if (cvParam.getAccession().equalsIgnoreCase("MS:1000744") || cvParam.getAccession().equalsIgnoreCase("PSI:1000040")) { // precursor m/z
                precursorMz = new Double(cvParam.getValue());
            } else if (cvParam.getAccession().equalsIgnoreCase("MS:1000042") || cvParam.getAccession().equalsIgnoreCase("PSI:1000042")) { // precursor intensity
                precursorIntensity = new Double(cvParam.getValue());
            } else if (cvParam.getAccession().equalsIgnoreCase("MS:1000041") || cvParam.getAccession().equalsIgnoreCase("PSI:1000041")) { // precursor charge
                precursorCharge = new Integer(cvParam.getValue());
            }
        }
        if (precursorMz == null || precursorMz == 0.0) {
            try {
                throw new XMLConversionException(ConversionError.PRECURSORS_MZ_MISSING);
            } catch (XMLConversionException ex) {
                evaluateError(ex);
            }
            return false;
        }
        if (precursorIntensity == null || precursorIntensity == 0.0) {
            precursorIntensity = 1.0;
            try {
                throw new XMLConversionException(ConversionError.PRECURSORS_INTENSITY_MISSING);
            } catch (XMLConversionException ex) {
                evaluateError(ex);
            }
        }
        if (precursorCharge == null || precursorCharge == 0) {
            precursorChargeAsString = "";
            try {
                throw new XMLConversionException(ConversionError.CHARGES_MISSING);
            } catch (XMLConversionException ex) {
                evaluateError(ex);
            }
        } else {
            precursorChargeAsString = precursorCharge + "+";
        }
        return true;
    }

    private boolean validateSpectraValues(Spectrum spectrum) throws NullPointerException, Exception {
        mzBinaryArray = spectrum.getMzNumberArray();
        intensityArray = spectrum.getIntentArray();
        if (mzBinaryArray == null || intensityArray == null) {
            try {
                if (mzBinaryArray == null || mzBinaryArray.length == 0) {
                    throw new XMLConversionException(ConversionError.SPECTRUM_MZ_MISSING);
                } else if (intensityArray == null || intensityArray.length == 0) {
                    throw new XMLConversionException(ConversionError.SPECTRUM_INTENSITY_MISSING);
                }
            } catch (XMLConversionException ex) {
                evaluateError(ex);
            }
            return false;
        }
        return true;
    }

    private boolean validateSpectrumParameters(Spectrum spectrum) throws NullPointerException, Exception {
        spectrumID = spectrum.getId();
        if (spectrumID == 0) {
            try {
                throw new XMLConversionException(ConversionError.SPECTRA_MALFORMED);
            } catch (XMLConversionException e) {
                evaluateError(e);
            }
            return false;
        }
        return true;
    }

    public boolean validateSpectrum(Spectrum spectrum) throws XMLConversionException, Exception {
        if (validatePrecursor(spectrum)
                && validateSpectraValues(spectrum)
                && validateSpectrumParameters(spectrum)) {
            return true;
        } else {
            return false;
        }
    }

    public void includeSpectrumInMgf(Spectrum spectrum, BufferedWriter bw) throws IOException, Exception {
//override for now untill more is clear about the intensity 
        precursorIntensity = 1.0;
        bw.write("BEGIN IONS" + System.getProperty("line.separator"));
        bw.write("TITLE=" + spectrumID + System.getProperty("line.separator"));
        bw.write("PEPMASS=" + precursorMz);
        bw.write("\t" + precursorIntensity + System.getProperty("line.separator"));
        bw.write("CHARGE=" + precursorCharge + "+" + System.getProperty("line.separator"));
        // process all peaks by iterating over the m/z values
        for (int i = 0; i < mzBinaryArray.length; i++) {
            bw.write(mzBinaryArray[i].toString());
            bw.write("\t");
            bw.write(intensityArray[i] + System.getProperty("line.separator"));
        }
        bw.write("END IONS" + System.getProperty("line.separator") + System.getProperty("line.separator"));
    }

    private boolean convertPrideXmlToMgf(File destinationMGF) {
        Boolean valid = true;
        FileWriter w = null;
        BufferedWriter bw = null;
        if (!destinationMGF.getParentFile().exists()) {
            destinationMGF.getParentFile().mkdir();
        }
        try {
            w = new FileWriter(destinationMGF);
            bw = new BufferedWriter(w);
            List<String> spectra = reader.getSpectrumIds();
            int spectraCount = spectra.size();

            if (spectraCount == 0) {
                //      valid = false;
                throw new XMLConversionException(ConversionError.SPECTRA_MISSING);
            } else {
                logger.debug(spectraCount + " spectra where discovered.");
            }

            for (String spectrumId : spectra) {
                int msLv = reader.getSpectrumMsLevel(spectrumId);
                if (msLv != 1) {
                    Spectrum spectrum = reader.getSpectrumById(spectrumId);
                    if (validateSpectrum(spectrum)) {
                        includeSpectrumInMgf(spectrum, bw);
                    }
                }
            }
            valid = true;
        } catch (XMLConversionException e) {
            evaluateError(e);
        } catch (IOException ex) {
            valid = false;
            //  ex.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
            valid = false;
        } finally {
            try {
                bw.close();
            } catch (Exception e) {
                if (bw != null) {
                    bw = null;
                }
            }
            try {
                w.close();
            } catch (Exception e) {
                if (w != null) {
                    w = null;
                }
            }
            return valid;
        }
    }

    public List<ConversionError> getErrorList() {
        return this.errorList;
    }

    public PrideXmlReader getPrideXmlReader() {
        return this.reader;
    }

    public boolean clearTempFiles() {
        boolean cleared = false;
        try {
            if (tempFile.exists() && zipped) {
                cleared = tempFile.delete();
                logger.debug("Cleared temporary files");
            }
        } catch (NullPointerException ex) {
            logger.debug("No temporary files were found");
        }
        return cleared;
    }

    public void evaluateError(XMLConversionException e) throws Exception {
        ConversionError error = e.getError();
        if (!errorList.contains(error)) {
            logger.error(error.toString());
            errorList.add(error);
            if (error.equals(error.PRECURSORS_INTENSITY_MISSING)) {
                logger.error("Precursor intensity could not be found, setting to 1.0");
            }
            if (error.equals(error.CHARGES_MISSING)) {
                logger.error("Charges could not be found, recommended to estimate with pride asa");
            }
            if (error.equals(error.PRECURSORS_MISSING)) {
                logger.error("No precursor information to be found, recommended to set defaults...");
            }

        }
    }
}
